/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package Client;

import Common.IClient;
import Common.IGameServer;
import Common.ImNotMasterServerException;
import Common.PlayernameAlreadyAssigned;
import Common.XMLReader;
import at.falb.games.alcatraz.api.Alcatraz;
import at.falb.games.alcatraz.api.MoveListener;
import at.falb.games.alcatraz.api.Player;
import at.falb.games.alcatraz.api.Prisoner;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NoSuchObjectException;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JOptionPane;

/**
 *
 * @author Mario,Thomas,Max,Martin,Sergio
 */
public class ClientImpl extends UnicastRemoteObject implements IClient, MoveListener
{

    private Alcatraz _alc = new Alcatraz();
    private Serializable _stub = null;
    private RegisterForm _regForm;
    private String _playerName;
    private Vector<IClient> _allClientsInGame;
    private IGameServer _currentMasterServer;

    // Implementations must have an
    // explicit constructor
    // in order to declare the
    // RemoteException exception
    public ClientImpl() throws RemoteException
    {
        super();
    }

    public void StartGame()
    {
        XMLReader reader = new XMLReader("client.config");
        _regForm = new RegisterForm(this);
        _regForm.FillServerComboBox(reader.ReadServerNames());
        try
        {
            //Ich muss hier toStub machen, weil ich dann auf der ServerSeite meine Stubs an die anderen Server verteile
            _stub = (Serializable) ClientImpl.toStub(this);
            //Laut Herrn Froihofer muss ich hier nicht toStub machen, weil es die darunterligende RMI schicht für mich macht
            //_stub = this;
        }
        catch (NoSuchObjectException ex)
        {
            Logger.getLogger(ClientImpl.class.getName()).log(Level.SEVERE, null, ex);
        }
        _regForm.setVisible(true);
    }

    public Serializable GetStub()
    {
        return _stub;
    }

    public Boolean Register(String serverName, String playerName)
    {
        try
        {
            IGameServer server = (IGameServer) Naming.lookup("rmi://" + serverName + "/AlcatrazServer");
            _playerName = playerName;
            try
            {
                server.registerClient((IClient) _stub);
                _currentMasterServer = server;          //Nach dem der Aufruf erfolgreich war--> ist es der MasterServer
            }
            catch (ImNotMasterServerException ex)
            {
                server = ex.GetMasterServer();
                try
                {
                    server.registerClient((IClient) this);
                    _currentMasterServer = server;      //Nach dem der Aufruf erfolgreich war--> ist es der MasterServer
                }
                catch (ImNotMasterServerException ex1)
                {
                    //Nach zwei mal scheiß ma drauf
                    Logger.getLogger(ClientImpl.class.getName()).log(Level.SEVERE, null, ex1);
                    return false;
                }
                catch (PlayernameAlreadyAssigned exname)
                {
                    JOptionPane.showMessageDialog(this._alc.getWindow(), "Spielername bereits vergeben. Anderen Namen wählen.", "Fehler", JOptionPane.OK_OPTION);
                    _playerName = "PlayerX";
                    return false;
                }
            }
            catch (PlayernameAlreadyAssigned exname)
            {
                JOptionPane.showMessageDialog(this._alc.getWindow(), "Spielername bereits vergeben. Anderen Namen wählen.", "Fehler", JOptionPane.OK_OPTION);
                _playerName = "PlayerX";
                return false;
            }

            
        }
        catch (NotBoundException ex)
        {
            Logger.getLogger(RegisterForm.class.getName()).log(Level.SEVERE, null, ex);
            return false;
        }
        catch (MalformedURLException ex)
        {
            Logger.getLogger(RegisterForm.class.getName()).log(Level.SEVERE, null, ex);
            return false;
        }
        catch (RemoteException ex)
        {
            Logger.getLogger(RegisterForm.class.getName()).log(Level.SEVERE, null, ex);
            return false;
        }
        return true;
    }

    public Boolean ForceStartGame(String serverName, String playerName)
    {
        try
        {
            IGameServer server = (IGameServer) Naming.lookup("rmi://" + serverName + "/AlcatrazServer");
            try
            {
                server.startGameNow((IClient) _stub);
                _currentMasterServer = server;          //Nach dem der Aufruf erfolgreich war--> ist es der MasterServer
            }
            catch (ImNotMasterServerException ex)
            {
                server = ex.GetMasterServer();
                try
                {
                    server.startGameNow((IClient) this);
                    _currentMasterServer = server;      //Nach dem der Aufruf erfolgreich war--> ist es der MasterServer
                }
                catch (ImNotMasterServerException ex1)
                {
                    //Nach zwei mal scheiß ma drauf
                    Logger.getLogger(ClientImpl.class.getName()).log(Level.SEVERE, null, ex1);
                    return false;
                }
                catch (PlayernameAlreadyAssigned exname)
                {
                    JOptionPane.showMessageDialog(this._alc.getWindow(), "Spielername bereits vergeben. Anderen Namen wählen.", "Fehler", JOptionPane.OK_OPTION);
                    _playerName = "PlayerX";
                    return false;
                }
            }
            catch (PlayernameAlreadyAssigned exname)
            {
                JOptionPane.showMessageDialog(this._alc.getWindow(), "Spielername bereits vergeben. Anderen Namen wählen.", "Fehler", JOptionPane.OK_OPTION);
                _playerName = "PlayerX";
                return false;
            }
            _playerName = playerName;
        }
        catch (NotBoundException ex)
        {
            Logger.getLogger(RegisterForm.class.getName()).log(Level.SEVERE, null, ex);
            return false;
        }
        catch (MalformedURLException ex)
        {
            Logger.getLogger(RegisterForm.class.getName()).log(Level.SEVERE, null, ex);
            return false;
        }
        catch (RemoteException ex)
        {
            Logger.getLogger(RegisterForm.class.getName()).log(Level.SEVERE, null, ex);
            return false;
        }
        return true;
    }

    public void Unregister(String serverName)
    {
        try
        {
            IGameServer server = null;
            if (_currentMasterServer != null)
            {
                server = _currentMasterServer;      //Wenn wir registriert waren, haben wir immer den aktuellen Master, dann brauchen wir keinen Lookup machen
            }
            else
            {
                server = (IGameServer) Naming.lookup("rmi://" + serverName + "/AlcatrazServer");
            }

            try
            {
                server.unregisterClient((IClient) _stub);
                _currentMasterServer = server;          //Nach dem der Aufruf erfolgreich war--> ist es der MasterServer
            }
            catch (ImNotMasterServerException ex)
            {
                server = ex.GetMasterServer();
                try
                {
                    server.unregisterClient((IClient) this);
                    _currentMasterServer = server;          //Nach dem der Aufruf erfolgreich war--> ist es der MasterServer
                }
                catch (ImNotMasterServerException ex1)
                {
                    Logger.getLogger(ClientImpl.class.getName()).log(Level.SEVERE, null, ex1);
                }
            }
            _playerName = "";   //Rücksetzen
        }
        catch (NotBoundException ex)
        {
            Logger.getLogger(RegisterForm.class.getName()).log(Level.SEVERE, null, ex);
        }
        catch (MalformedURLException ex)
        {
            Logger.getLogger(RegisterForm.class.getName()).log(Level.SEVERE, null, ex);
        }
        catch (RemoteException ex)
        {
            Logger.getLogger(RegisterForm.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public Boolean IsAlive() throws RemoteException
    {
        return true;
    }

    public synchronized void DoRemoteMove(Player player, Prisoner prisoner, int rowOrCol, int row, int col) throws RemoteException
    {
        try
        {
            _alc.doMove(_alc.getPlayer(player.getId()), _alc.getPrisoner(prisoner.getId()), rowOrCol, row, col);
        }
        catch (Exception ex)
        {
            Logger.getLogger(ClientImpl.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public synchronized void DoStartGame(Vector<IClient> others, IClient firstPlayer) throws RemoteException
    {
        _regForm.setVisible(false);
        _allClientsInGame = others;
        _alc.addMoveListener(this);
        _alc.init(others.size(), others.indexOf(this._stub));
        _alc.showWindow();
        _alc.start();

        for (IClient client : others)
        {
            _alc.getPlayer(others.indexOf(client)).setName(client.GetPlayerName());
        }

        StartIsAliveLoop();
    }

    public void AnnounceMasterServer(IGameServer masterserver) throws RemoteException
    {
        if (masterserver != null)
        {
            _currentMasterServer = masterserver;        //Wenn sich in der ServerGruppe ein neuer Master gewählt wird, werden wir benachrichtigt
        }
    }

    public String GetPlayerName() throws RemoteException
    {
        return _playerName;
    }

    /**
     * Initialisiert einen BackgroundThread welche jede Sekunde versucht alle anderen Clients
     * des Spiels zu erreichen. Sollte ein client nicht mehr erreichbar sein, so wird ein
     * Timout Mechansimus gestartet, nach ablauf des Timeouts ist das Spiel zu ende. Sollte
     * innerhalb des Timeouts der Client zurückkehren, geht das Spiel weiter.
     */
    public void StartIsAliveLoop()
    {
        Thread background = new Thread("IsAlive")
        {

            @Override
            public void run()
            {
                while (1 == 1)
                {
                    for (IClient client : _allClientsInGame)
                    {
                        if (!_stub.equals(client))   //Mir selbst brauche ich das nicht senden
                        {
                            boolean sucessfullySend = false;
                            int sendCounter = 0;
                            // timeout loop
                            while (sucessfullySend == false)
                            {
                                try
                                {
                                    // try to reach other client
                                    client.IsAlive();
                                    sucessfullySend = true;
                                    //Thread.sleep(1000);
                                    System.out.println("Thread alive...");
                                }
//                                catch (InterruptedException ex)
//                                {
//                                    // terminating Client;--> keine Ahnung wann so etwas kommt, wir haben damit auf jeden Fall nichts zu tun wir unterbrechen niemals einen Thread...
//                                    Logger.getLogger(ClientImpl.class.getName()).log(Level.SEVERE, "Serious System Problem. Could not pause Thread. Terminating...", ex);
//                                    JOptionPane.showMessageDialog(_alc.getWindow(), "Interrupted Exception des Background IsAliveThreads", "Interrupted Exception", JOptionPane.OK_OPTION);
//                                    System.exit(1);
//                                }
                                catch (RemoteException ex)
                                {
                                    waitForTimeOut(ex, ++sendCounter);
                                }
                            }
                        }
                    }
                }
            }
        };
        // start the background thread
        background.start();
    }

    /**
     * Diese Methode kümmert sich um den TimeoutMechanismus!
     */
    private void waitForTimeOut(RemoteException ex, int sendCounter)
    {
        Logger.getLogger(ClientImpl.class.getName()).log(Level.WARNING, "Try to reach ohter client...", "");
        if (sendCounter == IClient.MAX_SENDTRIES)
        {
            // terminating Client;
            Logger.getLogger(ClientImpl.class.getName()).log(Level.SEVERE, "Unable to reach ohter client... Giving up...", ex);
            JOptionPane.showMessageDialog(this._alc.getWindow(), "Ein Spieler hat das Spiel unerwartet verlassen. ", "Spiel beendet", JOptionPane.OK_OPTION);
            System.exit(1);
        }
        try
        {
            Thread.sleep(IClient.WAIT_PERIOD);
        }
        catch (InterruptedException ex1)
        {
            // terminating Client;--> keine Ahnung wann so etwas kommt, wir haben damit auf jeden Fall nichts zu tun wir unterbrechen niemals einen Thread...
            Logger.getLogger(ClientImpl.class.getName()).log(Level.SEVERE, "Serious System Problem. Could not pause Thread. Terminating...", ex1);
            JOptionPane.showMessageDialog(_alc.getWindow(), "Interrupted Exception des Background IsAliveThreads", "Interrupted Exception", JOptionPane.OK_OPTION);
            System.exit(1);
        }
    }

    /**
     *  Lokale Methode vom Move Listener
     */
    public void doMove(Player arg0, Prisoner arg1, int arg2, int arg3, int arg4)
    {
        for (IClient other : _allClientsInGame)
        {
            if (!this._stub.equals(other))   //Mir selbst brauche ich das nicht senden
            {
                boolean sucessfullySend = false;
                int sendCounter = 0;
                // timeout loop--> weil es kann sein, das ein Client jetzt gerade weg ist
                while (sucessfullySend == false)
                {
                    try
                    {
                        other.DoRemoteMove(arg0, arg1, arg2, arg3, arg4);
                        sucessfullySend = true;
                    }
                    catch (RemoteException ex)
                    {
                        waitForTimeOut(ex, ++sendCounter);
                    }
                }
            }
        }
    }

    public void undoMove()
    {
        throw new UnsupportedOperationException("Rückgängig gibt es bei uns nicht!");
    }

    public void gameWon(Player arg0)
    {
        JOptionPane.showMessageDialog(this._alc.getWindow(), "Player " + arg0.getName() + " wins the game!", "Alcatraz Client", JOptionPane.OK_OPTION);
    }
}
